/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/variable/FunctionWrapper.h"
#include "simodo/bormental/DrBormental.h"
#include "simodo/inout/format/fmt.h"

#include <cassert>

namespace simodo::variable
{
    inline const std::string INVALID_FUNCTION_STRUCTURE = "Invalid function internal structure";

    FunctionWrapper::FunctionWrapper(const Variable & function_variable)
        : _function_variable(function_variable.origin())
        , _function_structure(_function_variable.value().getObject())
    {
        // assert(_function_variable.type() == ValueType::Function);
    }

    Value FunctionWrapper::invoke(VariableSetWrapper_mutable & arguments)
    {
        Variable calling_address = getCallingAddressVariable();

        castArguments(arguments);

        if (calling_address.type() == ValueType::ExtFunction) {
            ExternalFunction func         = std::get<ExternalFunction>(calling_address.value().variant());
            Value            return_value = func.f(func.m.get(), arguments);

            // return_variable.setLocation(_dot.location);
            // return _machine.convertVariable_overridable(return_variable, getReturnDeclarationVariable().type());
            return return_value;
        }

        // if (calling_address.type() == ValueType::IntFunction) {
        //     InternalFunction_t internal_function_address = get<InternalFunction_t>(calling_address.value().variant());

        //     StWalkerState state = _machine.host().walker().walk(*internal_function_address);

        //     return (state == StWalkerState::Error) ? error_variable() : _machine.return_variable();
        // }

        throw bormental::DrBormental("FunctionWrapper::invoke", inout::fmt(INVALID_FUNCTION_STRUCTURE));
    }

    void FunctionWrapper::castArguments(VariableSetWrapper_mutable & arguments) const 
    {
        VariableSetWrapper declarations = getArgumentDeclarationVariables();

        if (arguments.size() != declarations.size())
            // throw SemanticException("FunctionWrapper::castArguments", 
            //                         _dot.location,
            //                         "The number of given parameters does not match the number of declared arguments for function '" +
            //                         convertToU8(_function_variable.name()) + "'");
            throw bormental::DrBormental("FunctionWrapper::castArguments", inout::fmt(INVALID_FUNCTION_STRUCTURE));

        for(size_t i=0; i < arguments.size(); ++i) {
            if (arguments[i].origin().type() != declarations[i].type() && declarations[i].type() != ValueType::Null)
                // arguments[i] = _machine.convertVariable_overridable(arguments[i].origin(),declarations[i].type());
                throw bormental::DrBormental("FunctionWrapper::castArguments", inout::fmt(INVALID_FUNCTION_STRUCTURE));
            if (arguments[i].name() != declarations[i].name())
                arguments[i].setName(declarations[i].name());
        }
    }

    const Variable & FunctionWrapper::getCallingAddressVariable() const
    {
        if (_function_structure->variables().size() < 2)
            throw bormental::DrBormental("FunctionWrapper::getCallingAddressVariable", inout::fmt(INVALID_FUNCTION_STRUCTURE));

        return _function_structure->variables()[0];
    }

    const Variable & FunctionWrapper::getReturnDeclarationVariable() const
    {
        if (_function_structure->variables().size() < 2)
            throw bormental::DrBormental("FunctionWrapper::getReturnDeclarationVariable", inout::fmt(INVALID_FUNCTION_STRUCTURE));

        return _function_structure->variables()[1];
    }

    VariableSetWrapper FunctionWrapper::getArgumentDeclarationVariables() const
    {
        if (_function_structure->variables().size() < 2)
            throw bormental::DrBormental("FunctionWrapper::getArgumentDeclarationVariables", inout::fmt(INVALID_FUNCTION_STRUCTURE));

        return VariableSetWrapper(_function_structure->variables(), 2, _function_structure->variables().size());
    }

}